<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>管线流动特效</title>
    <style>
        canvas { border: 1px solid #ccc; }
    </style>
</head>
<body>
    <canvas id="pipelineCanvas" width="800" height="400"></canvas>
    <script>
        // 1. 初始化画布
        const canvas = document.getElementById('pipelineCanvas');
        const ctx = canvas.getContext('2d');

        // 2. 定义管道路径点 - 通过一系列坐标点定义管道的形状
        const pipelinePath = [
            { x: 50, y: 200 }, { x: 600, y: 200 }, { x: 600, y: 300 }, { x: 50, y: 300 },{ x: 50, y: 200 }
        ];

        // 3. 设置粒子系统的基本参数
        const particles = [];        // 存储所有粒子的数组
        const particleCount = 5;     // 粒子数量(3个分段光点)
        const particleSpeed = 6;   // 粒子移动速度

        // 4. 定义粒子类 - 处理单个光点的属性和行为
        class Particle {
            constructor() {
                this.pos = Math.random();  // 初始随机位置(0-1之间)
                this.length = 0.1;         // 光点拖尾长度
                this.color = '#00ff00';    // 光点颜色
                this.opacity = 0.8;        // 光点透明度
            }

            // 4.1 更新粒子位置
            update() {
                this.pos += particleSpeed / 1000;  // 每帧移动一小段距离
                if (this.pos > 1) this.pos = 0;   // 到达终点后重置到起点
            }

            // 4.2 绘制粒子
            draw(ctx) {
                // 通过多个渐变透明的圆形构建拖尾效果
                for(let t = 0; t < this.length; t += 0.01) {
                    const pos = this.pos - t;      // 计算拖尾每个点的位置
                    if(pos < 0) continue;          // 跳过路径起点之前的位置
                    
                    const point = getPointOnPath(pos);  // 获取实际坐标
                    const alpha = (1 - t/this.length) * this.opacity;  // 计算透明度
                    
                    // 绘制发光圆点
                    // ctx.beginPath();
                    // ctx.arc(point.x, point.y, 4, 0, Math.PI * 2);
                    // ctx.fillStyle = `rgba(0, 255, 0, ${alpha})`;
                    // ctx.shadowBlur = 15;
                    // ctx.shadowColor = this.color;
                    // ctx.fill();

                    // 使用矩形
                    ctx.beginPath()
                    ctx.fillStyle = `rgba(0, 255, 0, ${alpha})`;
                    ctx.fillRect(point.x - 2, point.y - 2, 5,6);
                    ctx.fill();
                }
            }
        }

        // 5. 初始化粒子系统 - 创建指定数量的粒子
        for (let i = 0; i < particleCount; i++) {
            particles.push(new Particle());
        }

        // 6. 工具函数 - 计算路径上指定位置的具体坐标
        function getPointOnPath(t) {
            if(t < 0) return null;
            const totalLength = pipelinePath.length - 1;
            const segment = t * totalLength;           // 确定在哪个线段上
            const segmentIndex = Math.floor(segment);
            const segmentFraction = segment - segmentIndex;  // 线段内的具体位置

            // 计算插值坐标
            if (segmentIndex >= totalLength) return pipelinePath[pipelinePath.length - 1];
            const start = pipelinePath[segmentIndex];
            const end = pipelinePath[segmentIndex + 1];
            return {
                x: start.x + (end.x - start.x) * segmentFraction,
                y: start.y + (end.y - start.y) * segmentFraction
            };
        }

        // 7. 绘制静态管道
        function drawPipeline() {
            ctx.beginPath();
            ctx.moveTo(pipelinePath[0].x, pipelinePath[0].y);
            for (let i = 1; i < pipelinePath.length; i++) {
                ctx.lineTo(pipelinePath[i].x, pipelinePath[i].y);
            }
            ctx.strokeStyle = '#333';
            ctx.lineWidth = 10;
            ctx.stroke();
        }

        // 8. 动画主循环
        function animate() {
            // 8.1 清除上一帧
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            // 8.2 绘制静态管道
            drawPipeline();
            // 8.3 更新并绘制所有粒子
            particles.forEach(particle => {
                particle.update();
                particle.draw(ctx);
            });
            // 8.4 请求下一帧动画
            requestAnimationFrame(animate);
        }

        // 9. 启动动画
        animate();
    </script>
</body>
</html>